/**
 * @file jse_env.h
 * @author pengyaozong (pengyaozong@xiaomi.com)
 * @brief javascript environment class
 * @version 0.1
 * @date 2021-07-23
 * 
 * @copyright Copyright (c) 2021
 * 
 */
#pragma once
#include <quickjspp.hpp>
#include <memory>
#include <mutex>
#include <queue>
#include <future>

namespace qjs
{
    class Runtime;
    class Context;
}

namespace uvw
{
    class Loop;
    class PrepareHandle;
    class CheckHandle;
    class IdleHandle;
    class AsyncHandle;
}
class CallableTaskBase
{
public:
    virtual ~CallableTaskBase(){};

    qjs::Value result() { return p.get_future().get(); }
    void result(const qjs::Value& r) {  p.set_value(r); }
    void execption(const std::exception_ptr& e) { p.set_exception(e); }

    virtual qjs::Value operator()() const = 0;

protected:
    std::promise<qjs::Value> p;
};

class javascript_environment : public std::enable_shared_from_this<javascript_environment>
{
    template <typename F, typename... Args>
    class CallableTask : public CallableTaskBase
    {
    private:
        std::tuple<Args...> args_;
        F&& func_;

    public:
        CallableTask(F &&f, Args &&...args)
            : func_(std::forward<F &&>(f)), args_(std::forward<Args>(args)...)
        {
        }

        ~CallableTask()
        {
        }

        qjs::Value operator()() const override
        {
            return std::apply(std::forward<F &&>(func_), args_);
        }
    };

private:
    std::shared_ptr<qjs::Runtime> rt_;
    std::shared_ptr<qjs::Context> ctx_;

    std::shared_ptr<uvw::Loop> loop_;
    std::shared_ptr<uvw::PrepareHandle> prepare_;
    std::shared_ptr<uvw::CheckHandle> check_;
    std::shared_ptr<uvw::AsyncHandle> quit_;
    std::shared_ptr<uvw::IdleHandle> idle_;

    std::mutex mtx_async_calls_;
    std::deque<std::shared_ptr<CallableTaskBase>> async_calls_;
    std::promise<bool> init_result_;

    /**
     * @brief 初始化javascript环境
     * 
     * @return true 
     * @return false 
     */
    bool init_jse();

    bool init_uv();

public:
    javascript_environment() = default;
    javascript_environment(javascript_environment &other) = delete;
    ~javascript_environment();
    /**
     * @brief init environment
     * 
     * @return true 
     * @return false 
     */
    bool init();
    /**
     * @brief 执行uv循环
     * 
     * @return true 
     * @return false 
     */
    bool exec();

    /**
     * @brief 停止运行
     * 
     */
    void stop();

    /**
     * @brief Get the Context object
     * 
     * @return std::shared_ptr<qjs::Context> 
     */
    std::shared_ptr<qjs::Context> get_context();

    /**
     * @brief 执行js代码
     * 
     * @param javascript 
     * @return qjs::Value 
     */
    qjs::Value eval_javascript_string(const std::string &javascript);


    qjs::Value eval_javascript_file(const std::string &jsfilepath);

    /**
     * @brief 执行异步调用，调用将在js线程中执行
     * 
     * @tparam F        回调函数类型
     * @tparam Args     参数类型
     * @param cb        回调函数
     * @param args      参数包
     * @return std::shared_ptr<CallableTaskBase> 调用对象，这里被转型成基类
     */
    template <typename F, typename... Args>
    std::shared_ptr<CallableTaskBase> execute_async_call(F &&cb, Args... args)
    {
        std::shared_ptr<CallableTaskBase> act = std::dynamic_pointer_cast<CallableTaskBase>(
            std::make_shared<CallableTask<F, Args...>>(std::forward<F &&>(cb), std::forward<Args>(args)...));
        std::lock_guard<std::mutex> lck(mtx_async_calls_);
        async_calls_.emplace_back(act);
        return act;
    }

    void set_init_result(bool result);
    bool get_init_result();
};